package sf.querydsl;

import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.PathMetadata;
import com.querydsl.core.types.PathMetadataFactory;
import com.querydsl.core.types.dsl.ArrayPath;
import com.querydsl.core.types.dsl.BooleanPath;
import com.querydsl.core.types.dsl.CollectionPath;
import com.querydsl.core.types.dsl.ComparablePath;
import com.querydsl.core.types.dsl.DatePath;
import com.querydsl.core.types.dsl.DateTimePath;
import com.querydsl.core.types.dsl.EnumPath;
import com.querydsl.core.types.dsl.ListPath;
import com.querydsl.core.types.dsl.MapPath;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.core.types.dsl.SetPath;
import com.querydsl.core.types.dsl.SimpleExpression;
import com.querydsl.core.types.dsl.SimplePath;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.core.types.dsl.TimePath;
import com.querydsl.sql.ColumnMetadata;
import com.querydsl.sql.RelationalPathBase;
import com.querydsl.sql.types.EnumByNameType;
import com.querydsl.sql.types.EnumByOrdinalType;
import com.querydsl.sql.types.Type;
import sf.core.DBField;
import sf.core.DBObject;
import sf.database.dao.DBContext;
import sf.database.meta.ColumnMapping;
import sf.database.meta.MetaHolder;
import sf.database.meta.TableMapping;
import sf.database.util.OrmUtils;
import sf.database.util.SQLUtils;

import javax.persistence.TemporalType;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * DBObject对应的QueryDSL模型.
 */
public class SQLRelationalPath<T extends DBObject> extends RelationalPathBase<T> implements Cloneable {
    /**
     *
     */
    private static final long serialVersionUID = 1L;


    /**
     * 字段列,值为SimpleExpression
     */
    protected final Map<DBField, Expression<?>> map = new HashMap<>();
    protected final Map<ColumnMapping, Type<?>> typeMap = new HashMap<>(10);

    protected Class<T> clz;

    public Map<DBField, Expression<?>> getMap() {
        return map;
    }

    public SQLRelationalPath(Class<? extends DBObject> type) {
        this(null,MetaHolder.getMeta(type));
    }

    protected SQLRelationalPath(DBContext context, Class<? extends DBObject> type, String variable) {
        this(context,MetaHolder.getMeta(type), variable);
    }

    public SQLRelationalPath(DBContext context,TableMapping tm) {
        this(context,tm, OrmUtils.getDynamicTableName(context,tm));
    }

    protected SQLRelationalPath(DBContext context,TableMapping tm, String variable) {
        super((Class<T>) tm.getThisType(), PathMetadataFactory.forVariable(variable), tm.getSchema(),
                OrmUtils.getDynamicTableName(context,tm));
        init(tm);
    }

    private void init(TableMapping tm) {
        this.clz = (Class<T>) tm.getThisType();
        for (Map.Entry<DBField, ColumnMapping> entry : tm.getSchemaMap().entrySet()) {
            Expression<?> path = getBeanMappingType(entry.getValue());
            addMetadata(entry.getValue(), (Path<?>) path);
            map.put(entry.getKey(), path);
        }
        createPrimaryKey(tm);
    }

    protected SQLRelationalPath(Class<? extends T> type, String variable, String schema, String table) {
        super(type, variable, schema, table);
    }

    protected SQLRelationalPath(Class<? extends T> type, PathMetadata metadata, String schema, String table) {
        super(type, metadata, schema, table);
    }

    protected void createPrimaryKey(TableMapping tm) {
        Path<?>[] pkPaths = new Path[tm.getPkFields().size()];
        List<ColumnMapping> pkFields = tm.getPkFields();
        for (int i = 0; i < pkFields.size(); i++) {
            ColumnMapping cm = pkFields.get(i);
            pkPaths[i] = (Path<?>) map.get(cm.getField());
        }
        if (pkPaths.length > 0) {
            createPrimaryKey(pkPaths);
        }
    }

    protected void addMetadata(Class<? extends T> type) {
        TableMapping tm = MetaHolder.getMeta(type);
        for (Map.Entry<DBField, ColumnMapping> entry : tm.getSchemaMap().entrySet()) {
            addMetadata(entry.getValue(), (Path<?>) map.get(entry.getKey()));
        }
    }

    protected void addMetadata(ColumnMapping cm, Path<?> path) {
        String columnName = SQLUtils.getNoWrapperColumnName(cm.getRawColumnName());
        ColumnMetadata cmd = ColumnMetadata.named(columnName).ofType(cm.getSqlType());
        if (cm.getColumn() != null) {
            cmd = cmd.withSize(cm.getColumn().length());
        }
        //添加RelationPath的表结构
        addMetadata(path, cmd);
    }

    /**
     * @param field
     * @param <P>
     * @return
     */
    public <P> Path<P> path(DBField field) {
        return column(field, Path.class);
    }

    public <P> Expression<P> expression(DBField field) {
        return column(field, Expression.class);
    }

    public BooleanPath bool(DBField field) {
        return column(field, BooleanPath.class);
    }

    public StringPath string(DBField field) {
        return column(field, StringPath.class);
    }

    /**
     * 抹去泛型类型
     * @param field
     * @return
     */
    @SuppressWarnings({"unchecked"})
    public <T extends Number & Comparable<?>> NumberPath<T> number(DBField field) {
        return column(field, NumberPath.class);
    }

    @SuppressWarnings({"unchecked"})
    public <T extends Enum<T>> EnumPath<T> enums(DBField field) {
        return column(field, EnumPath.class);
    }

    public <T> SimplePath<T> simple(DBField field) {
        return column(field, SimplePath.class);
    }

    public <A, E> ArrayPath<A, E> array(DBField field) {
        return column(field, ArrayPath.class);
    }

    public <T extends Comparable> ComparablePath<T> comparable(DBField field) {
        return column(field, ComparablePath.class);
    }

    public <T extends Comparable> DatePath<T> date(DBField field) {
        return column(field, DatePath.class);
    }

    public <T extends Comparable> DateTimePath<T> dateTime(DBField field) {
        return column(field, DateTimePath.class);
    }

    public <T extends Comparable> TimePath<T> time(DBField field) {
        return column(field, TimePath.class);
    }

    /**
     * 不支持
     * @param field
     * @return
     */
    private <E, Q extends SimpleExpression<? super E>> CollectionPath<E, Q> collection(DBField field) {
        return column(field, CollectionPath.class);
    }

    /**
     * 不支持
     * @param field
     * @return
     */
    private <E, Q extends SimpleExpression<? super E>> SetPath<E, Q> set(DBField field) {
        return column(field, SetPath.class);
    }

    /**
     * 不支持
     * @param field
     * @return
     */
    private <E, Q extends SimpleExpression<? super E>> ListPath<E, Q> list(DBField field) {
        return column(field, ListPath.class);
    }

    /**
     * 不支持
     * @param field
     * @return
     */
    private <K, V, E extends SimpleExpression<? super V>> MapPath<K, V, E> map(DBField field) {
        return column(field, MapPath.class);
    }

    @SuppressWarnings({"hiding", "unchecked"})
    protected <T> T column(DBField field, Class<T> clz) {
        Expression<?> path = map.get(field);
        if (path == null) {
            //字段不存在
            throw new RuntimeException("field not exists!");
        }
        if (clz != null) {
            if (clz.isAssignableFrom(path.getClass())) {
                return (T) path;
            } else {
                //类型转换错误
                throw new ClassCastException("field cast error!");
            }
        } else {
            return (T) path;
        }
    }

    protected Expression<?> getBeanMappingType(ColumnMapping cm) {
        String fieldName = cm.getFieldName();
        Class<?> mirror = cm.getClz();
        TemporalType temporalType = cm.getTemporal() == null ? null : cm.getTemporal().value();
        // String and char
        if (mirror == String.class) {
            return createString(fieldName);
        }
        // Boolean
        if (mirror == Boolean.class || mirror == Boolean.TYPE) {
            return createBoolean(fieldName);
        }
        // Byte
        if (mirror == Byte.class || mirror == Byte.TYPE) {
            return createNumber(fieldName, Byte.class);
        }
        // Short
        if (mirror == Short.class || mirror == Short.TYPE) {
            return createNumber(fieldName, Short.class);
        }
        // Int
        if (mirror == Integer.class || mirror == Integer.TYPE) {
            return createNumber(fieldName, Integer.class);
        }
        // Float
        if (mirror == Float.class || mirror == Float.TYPE) {
            return createNumber(fieldName, Float.class);
        }
        // Double
        if (mirror == Double.class || mirror == Double.TYPE) {
            return createNumber(fieldName, Double.class);
        }
        // Long
        if (mirror == Long.class || mirror == Long.TYPE) {
            return createNumber(fieldName, Long.class);
        }
        // BigDecimal
        if (mirror == BigDecimal.class) {
            return createNumber(fieldName, BigDecimal.class);
        }
        // BigInteger
        if (mirror == BigInteger.class) {
            return createNumber(fieldName, BigInteger.class);
        }

        // Enum
        if (mirror.isEnum()) {
            if (cm.getEnumerated() != null) {
                switch (cm.getEnumerated().value()) {
                    case ORDINAL:
                        typeMap.put(cm, new EnumByNameType(mirror));
                        break;
                    case STRING:
                        typeMap.put(cm, new EnumByOrdinalType(mirror));
                        break;
                    default:
                        break;
                }
            }
            return createEnum(fieldName, (Class) mirror);
        }
        // Char
        if (mirror == CharSequence.class || mirror == Character.TYPE) {
            return createSimple(fieldName, Character.class);
        }

        // jdk8日期
        if (mirror == LocalDate.class) {
            return createDate(fieldName, LocalDate.class);
        }
        if (mirror == LocalDateTime.class) {
            return createDateTime(fieldName, LocalDateTime.class);
        }
        if (mirror == LocalTime.class) {
            return createTime(fieldName, LocalTime.class);
        }
        // 日期处理
        if (Date.class.isAssignableFrom(mirror)) {
            if (temporalType != null) {
                switch (temporalType) {
                    case DATE:
                        return createDate(fieldName, (Class<? super Comparable>) mirror);
                    case TIME:
                        return createTime(fieldName, (Class<? super Comparable>) mirror);
                    case TIMESTAMP:
                        return createDateTime(fieldName, (Class<? super Comparable>) mirror);
                    default:
                        break;
                }
            }
            return createDateTime(fieldName, (Class<? super Comparable>) mirror);
        }

        // byte[]
        if (mirror.isArray()) {
            return createArray(fieldName, (Class<? super Object>) mirror);
        }
        // 默认情况
        return createSimple(fieldName, (Class<? super Object>) mirror);
    }

    /**
     * 新创建
     * @param field
     * @return
     */
    public StringPath stringNew(DBField field) {
        TableMapping tm = MetaHolder.getMeta(this.clz);
        ColumnMapping cm = tm.getSchemaMap().get(field);
        if (cm == null) {
            throw new RuntimeException("field not found");
        }
        String fieldName = cm.getFieldName();
        StringPath path = createString(fieldName);
//        addMetadata(cm, path);
        return path;
    }

    /**
     * 新创建
     * @param field
     * @param tClass
     * @param <T>
     * @return
     */
    public <T extends Number & Comparable<?>> NumberPath<T> numberNew(DBField field, Class<T> tClass) {
        TableMapping tm = MetaHolder.getMeta(this.clz);
        ColumnMapping cm = tm.getSchemaMap().get(field);
        if (cm == null) {
            throw new RuntimeException("field not found");
        }
        String fieldName = cm.getFieldName();
        NumberPath<T> path = createNumber(fieldName, tClass);
//        addMetadata(cm, path);
        return path;
    }

    /**
     * 新创建
     * @param field
     * @return
     */
    public BooleanPath booleanNew(DBField field) {
        TableMapping tm = MetaHolder.getMeta(this.clz);
        ColumnMapping cm = tm.getSchemaMap().get(field);
        if (cm == null) {
            throw new RuntimeException("field not found");
        }
        String fieldName = cm.getFieldName();
        BooleanPath path = createBoolean(fieldName);
//        addMetadata(cm, path);
        return path;
    }

    public Class<T> getClz() {
        return clz;
    }

    protected void setClz(Class<T> clz) {
        this.clz = clz;
    }

    public Map<ColumnMapping, Type<?>> getUnmodifiableTypeMap() {
        return Collections.unmodifiableMap(typeMap);
    }

    /**
     * 克隆一个新的原始对象.
     * @param <T>
     * @return
     */
    @SuppressWarnings({"rawtypes", "unchecked", "hiding"})
    public <T extends DBObject> SQLRelationalPath<T> cloneNew() {
        return (SQLRelationalPath<T>) new SQLRelationalPath(clz);
    }

    @SuppressWarnings({"rawtypes", "unchecked", "hiding"})
    public <T extends DBObject> SQLRelationalPath<T> cloneNew(DBContext context,String variable) {
        return (SQLRelationalPath<T>) new SQLRelationalPath(context,clz, variable);
    }

}
